package com.android.common.image;

import java.io.InputStream;
import java.util.HashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;

import com.android.common.App;
import com.android.common.image.ImageLoader.ImageAttribute;
import com.android.common.network.HttpDownloader;
import com.android.common.network.HttpResult;
import com.android.common.security.EncryptUtil;
import com.android.common.util.FileUtil;
import com.android.common.util.ImageUtil;
import com.android.common.util.StringUtil;

/**
 * 负责生产Drawable和回收工作,控制bitmap内存使用
 * 
 * @author panjunlin
 * 
 */
public class DrawableFactory {

	private final static String TAG = "DrawableFactory";

	public static int MAX_IMG_SIZE;

	public static int BUFFER_IMG_SIZE;

	public static int SINGLE_SIZE;

	private static DrawableFactory instance;

	private static AtomicInteger curSize = new AtomicInteger(0);

	private final static HashMap<CreatorType, Creator> creators = new HashMap<CreatorType, Creator>();

	private final static LinkedBlockingQueue<BitmapExtDrawable> drawableQueue = new LinkedBlockingQueue<BitmapExtDrawable>(
			128);

	private DrawableFactory() {
		creators.put(CreatorType.resources, new ResourcesDrawableCreator());
		creators.put(CreatorType.network, new NetworkDrawableCreator());
		creators.put(CreatorType.local, new LocalDrawableCreator());
	}

	public static DrawableFactory getInstance() {
		if (instance == null) {
			instance = new DrawableFactory();
		}
		return instance;
	}

	public Drawable create(ImageAttribute att) {
		Drawable result = getCreator(att.url).create(att);

		if (result == null) {
			att.url = att.errorIcon;
			result = creators.get(CreatorType.resources).create(att);
		}

		if (att.convertor != null && result instanceof BitmapDrawable) {
			Bitmap b = ((BitmapDrawable) result).getBitmap();
			BitmapExtDrawable tmp = new BitmapExtDrawable(
					att.convertor.convertTo(b));
			if (result instanceof BitmapExtDrawable) {
				((BitmapExtDrawable) result).recycle();
			}
			offerDrawable(tmp);
			result = tmp;
		}

		return result;
	}

	private Creator getCreator(Object url) {
		Creator result = null;
		if (url instanceof Integer) {
			result = creators.get(CreatorType.resources);
		} else if (url instanceof String
				&& StringUtil.objectToString(url).startsWith("http://")) {
			result = creators.get(CreatorType.network);
		} else {
			result = creators.get(CreatorType.local);
		}
		return result;
	}

	/**
	 * 图片创建接口
	 * 
	 */
	private interface Creator {
		Drawable create(ImageAttribute att);
	}

	private class MemoryQueueDrawableCreator implements Creator {

		@Override
		public Drawable create(ImageAttribute att) {
			// if (drawableQueue.contains(drawable)) {
			// drawableQueue.remove(drawable);
			// }
			// TODO 从队列中拿，将其移动到队列尾部
			return null;
		}

	}

	/**
	 * 本地图片创建
	 */
	private class LocalDrawableCreator implements Creator {

		@Override
		public Drawable create(ImageAttribute att) {
			final String path = StringUtil.objectToString(att.url);
			if (StringUtil.isBlank(path)) {
				return null;
			}
			final int width = att.width > 0 ? att.width
					: App.DISPLAY.SCREEN_WIDTH;
			Log.d(TAG, "imageView width:" + width);
			BitmapFactory.Options opts = new BitmapFactory.Options();
			computeSampleSize(opts, path, width);// 设置SampleSize
			return createDrawable(opts, path, att);
		}

		private void computeSampleSize(BitmapFactory.Options opts, String path,
				int width) {
			InputStream is = null;
			try {
				is = FileUtil.readFile(path);
				if (is == null) {
					return;
				}
				opts.inJustDecodeBounds = true;
				opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
				BitmapFactory.decodeStream(is, null, opts);
				opts.inSampleSize = ImageUtil.computeSampleSize(opts, width,
						SINGLE_SIZE / 4);
				Log.d(TAG, "bitmap inSampleSize:" + opts.inSampleSize);
				opts.inJustDecodeBounds = false;
			} catch (Exception e) {
				Log.e(TAG, "computeSampleSize error:", e);
			} finally {
				FileUtil.closeInputStream(is);
			}
		}

		private Drawable createDrawable(BitmapFactory.Options opts,
				String path, ImageAttribute att) {
			BitmapExtDrawable mDrawable = null;
			InputStream is = null;
			Bitmap bitmap = null;
			try {
				is = FileUtil.readFile(path);
				if (is == null) {
					return null;
				}
				opts.inJustDecodeBounds = false;
				bitmap = BitmapFactory.decodeStream(is, null, opts);
			} catch (OutOfMemoryError e) {
				Log.e(TAG, "createDrawable error:", e);
			} finally {
				FileUtil.closeInputStream(is);
			}
			if (bitmap != null) {
				mDrawable = new BitmapExtDrawable(bitmap);
				offerDrawable(mDrawable);
			}
			return mDrawable;
		}
	}

	/**
	 * 网络图片创建
	 */
	private class NetworkDrawableCreator implements Creator {

		@Override
		public Drawable create(ImageAttribute att) {
			final String tmpUrl = StringUtil.objectToString(att.url);
			att.url = FileUtil.getImgPath(EncryptUtil.md5Hex(att.getUrl()));
			Drawable mDrawable = creators.get(CreatorType.local).create(att);
			if (mDrawable != null) {
				return mDrawable;
			}
			att.url = tmpUrl;
			final String url = att.getUrl();
			HttpResult result = HttpDownloader.getConnectInputStream(url);
			if (result.isSuccess())
				att.url = FileUtil.saveToLocalFileByUrl(url, result.input);
			return creators.get(CreatorType.local).create(att);
		}

	}

	/**
	 * 资源图片创建
	 */
	private class ResourcesDrawableCreator implements Creator {
		@Override
		public Drawable create(ImageAttribute att) {
			return App.getDrawable((Integer) att.url);
		}
	}

	private enum CreatorType {
		resources, network, local;
	}

	public class BitmapExtDrawable extends BitmapDrawable {

		public BitmapExtDrawable(Bitmap bp) {
			super(App.getResources(), bp);
			curSize.addAndGet(bitmapSize());
			Log.v(TAG, "add bitmap:" + bitmapSize() + ",use heap size:"
					+ curSize.get());
		}

		/**
		 * 重载了draw,catch在recycle后的图片上的异常
		 */
		@Override
		public void draw(Canvas canvas) {
			try {
				super.draw(canvas);
			} catch (Throwable t) {
				Log.e(TAG, "TaobanDrawable draw error:", t);
			}
		}

		public boolean isRecycled() {
			Bitmap bp = getBitmap();
			if (bp != null && !bp.isRecycled()) {
				return false;
			}
			return true;

		}

		public boolean recycle() {
			Bitmap bp = getBitmap();
			if (bp != null && !bp.isRecycled()) {

				final int before = curSize.getAndAdd(-bitmapSize());

				Log.v(TAG, "before heap size:" + before + ",recycle bitmap:"
						+ bitmapSize() + ",heap use size:" + curSize.get()
						+ ",max size:" + MAX_IMG_SIZE);
				bp.recycle();
				return true;
			}
			return false;
		}

		public int bitmapSize() {
			int size = 0;
			Bitmap bp = getBitmap();
			if (bp != null) {
				if (bp.isRecycled())
					return 0;
				// 用RawBytes来判断
				size = bp.getRowBytes() * bp.getHeight();
			}
			return size;
		}
	}

	private synchronized void offerDrawable(BitmapExtDrawable drawable) {
		if (curSize.get() >= MAX_IMG_SIZE) {
			ImageLoader.getInstance().pause();
			while (curSize.get() >= BUFFER_IMG_SIZE) {
				BitmapExtDrawable d = drawableQueue.poll();
				if (d != null) {
					d.recycle();
				}
			}
			ImageLoader.getInstance().resume();
		}
		if (!drawableQueue.offer(drawable)) {
			Log.v(TAG, "drawableQueue size:" + drawableQueue.size());
			drawableQueue.poll().recycle();
			drawableQueue.offer(drawable);
		}
	}

	/**
	 * 设置图片缓存大小，单位M
	 * 
	 * @param size
	 */
	public static void setImageSize(int size) {
		Log.v(TAG, "max image size:" + size + "M");
		MAX_IMG_SIZE = 1024 * 1024 * size;
		BUFFER_IMG_SIZE = MAX_IMG_SIZE * 2 / 3;
		SINGLE_SIZE = MAX_IMG_SIZE / 10;
	}

	public Drawable addQueue(Bitmap b) {
		BitmapExtDrawable d = new BitmapExtDrawable(b);
		offerDrawable(d);
		return d;
	}
}
